<?php
/**
 * 
 * @author Robert Hahn
 * @version 02/21/2011
 *        __  _____    ___   ___
 * |\  | |  |   |   | |     |
 * | \ | |  |   |   | |     |--
 * |  \| |__|   |   | |___  |___
 * 
 * the function "getUser()" MUST be called in the app controllers beforeFilter function
 * @example 
	function beforeFilter()	{
		$this->Snapuser->useFacebook('X-X-X-X-X-X', 'X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X', 'facebook_uid');
		$this->Snapuser->getUser();
		
		if ($this->Snapuser->newFacebookUser())	{ // there is a new user!
			if ($this->params['controller'] != 'users' || $this->params['action'] != 'signup')	
				$this->redirect(array('controller' => 'users', 'action' => 'signup'));
		}
	}	 
 * 
 * 
 * 
 * user the method setOptions to override the default options
 * @default
 * 		the default options are
 * 		array(
 * 			'username' => 'username', #column name
 * 			'password' => 'password', #column name
 * 			'salt' => 'salt',  #column name OR boolean (false if you do not want to salt the pass)
 * 			'cookies' => true, #boolean (false if you do not want to use cookie support; integer sets the cookie lifetime)
 * 			'hash' => 'md5', #boolean OR algorithm name (false if you do not want to hash your pass; true will use md5 algo)
 * 			'model' => 'User', #model name
 * 		)
 * 		In most of the Use-Cases these settings should fit
 * 	
 * But if you want to you can override the default settings by calling setOptions
 * 
 * @example 
 * 		setOptions( array('username' => 'email') )
 * 		You can login with your email.
 *
 * 		setOptions( array('salt' => false, 'username' => 'username', 'hash' => 'sha1') )
 * 		You can login with your username. The password will not be salted but hashed by sha1 algo
 * 
 * 		setOptions( array('salt' => 'salt', 'username' => 'username', 'hash' => 'md5') )
 * 		You can login with your username. The password will be salted with the value in the column 'salt' and then hashed by md5 algo
 * 
 * 
 *  SQL - CREATE table
 * 
	CREATE TABLE users (
	  id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
	  uid VARCHAR(20) NULL,
	  username VARCHAR(25) NULL,
	  email VARCHAR(100) NULL,
	  password VARCHAR(100) NULL,
	  salt VARCHAR(20) NULL,
	  update_date INTEGER UNSIGNED NULL,
	  create_date INTEGER UNSIGNED NULL,
	  PRIMARY KEY(id)
	);
 */
class SnapuserComponent extends Object	{
	
	var $name = 'Snapuser'; 
	var $components = array('Session', 'Cookie', 'Snapfacebook', 'Snaptwitter'); 
	var $options = array();
	var $user = array();
	var $usefacebook = false;
	var $usetwitter = false;
	
	/**
	 * called when cake creates an instance of this component
	 * @param $AppController $controller
	 */
	function initialize(&$controller) {
		// references to the app_controller
		$this->controller = &$controller; 
		// set the default values
		$this->setOptions(
			array(
				'password' => 'password',
				'username' => 'username',
				'salt' => 'salt',
				'cookies' => true,
				'cookiekey' => 'hG63$87{jahqZetrSd23',
				'hash' => 'md5',
				'model' => 'User',
				'fb_uid' => 'facebook_uid',
				'tw_uid' => 'twitter_uid',
			)
		);
		
		// some cookie settings
		$this->Cookie->name = 'appcookie';
	  	$this->Cookie->time =  '365 Days';
	  	$this->Cookie->path = '/'; 
	  	$this->Cookie->domain = $_SERVER['HTTP_HOST'];   
	  	$this->Cookie->secure = false;
	  	$this->Cookie->key = $this->options['cookiekey'];
	}
	
	/**
	 * @return true if the facebook user wasnt found in the database
	 */
	function newFacebookUser()	{
		return ($this->Session->check('facebookuser') && $this->Session->check('firsttime') && ($this->user == null || count($this->user) == 0));
	}
	
	/**
	 * @return true if the twitter user wasnt found in the database
	 */
	function newTwitterUser()	{
		return ($this->Session->check('twitteruser') && $this->Session->check('firsttime') && ($this->user == null || count($this->user) == 0));
	}
	
	/**
	 * if you want to, you can use facebooks "Facebook Connect" to login
	 * if you setup a backend for a normal login, you can login with facebook AND as normal user
	 * @param string $appId facebook APP-ID
	 * @param string $secretKey facebook SECRET-KEY
	 * @param string $fbuidField the column name in your users table where the facebook-userid is stored
	 */
	function useFacebook($appId, $secretKey, $fbuidField='facebook_uid')	{		
		$this->setOptions(array('fb_uid' => $fbuidField));
		$this->usefacebook = true;
		$this->Session->write('usefacebook', true);
		$this->Snapfacebook->setOptions(array('appid' => $appId, 'secretkey' => $secretKey));
	}
	

	function useTwitter($appId, $secretKey, $twuidField='twitter_uid')	{		
		$this->setOptions(array('tw_uid' => $twuidField));
		$this->usetwitter = true;
		$this->Session->write('usetwitter', true);
		$this->Snaptwitter->setOptions(array('appid' => $appId, 'secretkey' => $secretKey));
	}
	
	/**
	 * set and override options
	 * @param array $options
	 */
	public function setOptions( $options=array() )	{
		if (!is_array($options)) return;
		foreach($options as $key => $value)	
			$this->options[ $key ] = $value;
		return;
	}
	
	/**
	 * creates the cookie values
	 */
	private function getIdentify()	{
		$user = $this->user[$this->options['model']];
		$password = $user[$this->options['password']];
		$username = $user[$this->options['username']];
		return md5( md5( md5( $user['id'] ) ) ).md5( md5( md5( $password ) ) ).md5( md5( md5( $username ) ) );
	}
	
	/**
	 * if the user is loggedin session, cookies and more will be set
	 */
	private function setLoggedInStatus()	{
		$loggedin = $this->isLoggedIn(false);
		$this->controller->set('loggedin', $loggedin);
		$this->controller->set('loggedIn', $loggedin);
		if ($loggedin){
			$this->Session->write('loggedinUser', $this->user[$this->options['model']]['id']);
			$this->controller->set('sessionUser', $this->user);
			$this->Session->delete('facebookuser');
			$this->Session->delete('twitteruser');
			$this->Session->delete('firsttime');
			if (isset($this->options['cookies']))	{
				$this->Cookie->write('app_uid', $this->getIdentify());
			}
			return true;
		}
		return false;
	}
	
	/**
	 * NOTICE: THIS FUNCTION MUST BE CALLED IN THE APP-CONTROLLER "beforeFilter()" FUNCTION (mostly on top of the code block)
	 * grabs the user from
	 * + the session
	 * + a cookie
	 * + facebook
	 */
	function getUser()	{
			$this->controller->loadModel( $this->options['model'] );
		if ($this->Session->check('loggedinUser'))	{ // user is stored in a session
			eval(String::insert('$this->user = $this->controller->:model->findById(:id);', 
				array('model' => $this->options['model'], 'id' => $this->Session->read('loggedinUser')) )
			);
			return $this->setLoggedInStatus();
		} elseif(isset($this->Cookie->__values['app_uid'])) { // user is stored in a cookie
			$app_uid = $this->Cookie->read('app_uid');
			eval(
				String::insert(
					'$this->user = $this->controller->User->find("first", 
						array(
							"conditions" => array(
								"CONCAT( MD5(MD5(MD5(:model.id))), 
									MD5(MD5(MD5(:model.:password))), 
									MD5(MD5(MD5(:model.:username))) 
								)" => $app_uid
							)
						)
					);', array(
						'model' => $this->options['model'], 
						'username' => $this->options['username'], 
						'password' => $this->options['password']
					) 
				) 
			);
			return $this->setLoggedInStatus();
		} else	{
			if ($this->usefacebook &&  defined('FACEBOOK_APP_ID') && defined('FACEBOOK_SECRET_ID') && isset($this->options['fb_uid'])) { // facebook callback
				$facebookUser = $this->Snapfacebook->onLoginCallback(); // the user grabed by the Snapfacebook component
				
				if ($facebookUser != null)	{
					eval(String::insert('$this->user = $this->controller->:model->find("first", array("conditions" => array("'.$this->options['fb_uid'].'" => ":fbuid") ) );', 
						array('model' => $this->options['model'], 'fbuid' => $facebookUser->id) )
					);
					$this->controller->set('loggedin', $this->isLoggedIn(false));
					if (!$this->setLoggedInStatus() ){
						// new user
						$this->Session->write('facebookuser', $facebookUser);
						$this->Session->write('firsttime', true);
					}
					$this->controller->redirectHome();
				} elseif($this->Session->check('facebookuser')) {
					$facebookUser = $this->Session->read('facebookuser');
					eval(String::insert('$this->user = $this->controller->:model->find("first", array("conditions" => array("'.$this->options['fb_uid'].'" => ":fbuid") ) );', 
						array('model' => $this->options['model'], 'fbuid' => $facebookUser->id) )
					);
					$this->setLoggedInStatus();
				}
			}
			
			if ($this->usetwitter &&  defined('TWITTER_APP_ID') && defined('TWITTER_SECRET_ID') && isset($this->options['tw_uid'])) { // twitter callback
				$twitterUser = $this->Snaptwitter->onLoginCallback(); // the user grabed by the Snaptwitter component
				if ($twitterUser != null)	{
					eval(String::insert('$this->user = $this->controller->:model->find("first", array("conditions" => array("'.$this->options['tw_uid'].'" => ":twuid") ) );', 
						array('model' => $this->options['model'], 'twuid' => $twitterUser['user_id']) )
					);
					$this->controller->set('loggedin', $this->isLoggedIn(false));
					if (!$this->setLoggedInStatus() ){
						// new user
						$this->Session->write('twitteruser', $twitterUser);
						$this->Session->write('firsttime', true);
					}
					$this->controller->redirectHome();
				} elseif($this->Session->check('twitteruser')) {
					$twitterUser = $this->Session->read('twitteruser');
					eval(String::insert('$this->user = $this->controller->:model->find("first", array("conditions" => array("'.$this->options['tw_uid'].'" => ":twuid") ) );', 
						array('model' => $this->options['model'], 'twuid' => $twitterUser['user_id']) )
					);
				};
			}
		}
		
		$this->controller->set('loggedin', false);
		$this->controller->set('loggedIn', false);
		return false;
	}
	
	/**
	 * returns if there is a user logged in
	 * @param boolean $redirectToLogin if you want to redirect to a special location if the user isnt loggedin;
	 * 								   if true and no url is defined the user will be redirected to the page he was before login	
	 * @param array $url the location where the user should be redirected to if he isnt logged in
	 */
	function isLoggedIn($redirectToLogin=false, $url=false)	{
		$loggedin = $this->user && count($this->user) > 0;
		if ($loggedin)	{
			$this->Session->write('isfacebookuser', $this->usefacebook && $this->user[$this->options['model']][$this->options['fb_uid']] != '');
		}
		if (is_array($url) && count($url)>0)	{
			if (!isset($url['controller'])) $url['controller'] = $this->controller->params['controller'];
			if (!isset($url['action'])) $url['controller'] = $this->controller->params['action'];
			if (count($url) == 2)
			$url = array_merge($url, $this->controller->params['pass'], $this->controller->params['named']);
			$this->Session->write('nexturl', $url );
		} elseif(!is_bool($url) || $redirectToLogin)	{
			$url = array('controller' => $this->controller->params['controller'], 'action' => $this->controller->params['action'] );
			$url = array_merge($url, $this->controller->params['pass'], $this->controller->params['named']);
			$this->Session->write('nexturl', $url );
		}
		if (!$loggedin && $redirectToLogin)
			$this->controller->redirect(array('controller' => 'users', 'action' => 'login'));
		else {
			if ($loggedin)	{
				$this->controller->set('loggedin', true);
				$this->controller->set('sessionUser', $this->user);
			}
			return $loggedin;
		}
	}
	
	/**
	 * @return true if the current user account is linked with facebook
	 */
	function isFacebookUser()	{
		return $this->Session->check('facebookuser') || ($this->Session->check('isfacebookuser'));
	}
	
	/**
	 * @return object returns the facebook account as object given by the facebook request done with the accesstoken (OAuth)
	 */
	function getFacebookUser()	{
		return $this->Session->read('facebookuser');
	}
	
	/**
	 * @return true if the current user account is linked with twitter
	 */
	function isTwitterUser()	{
		return $this->Session->check('twitteruser') || ($this->Session->check('istwitteruser'));
	}
	
	/**
	 * @return object returns the twitter account as object given by the twitter request done with the accesstoken (OAuth)
	 */
	function getTwitterUser()	{
		return $this->Session->read('twitteruser');
	}
	
	/**
	 * call this function if you want to check if the given username and password match
	 * @return true if login was successfull
	 */
	function login()	{
		$model = $this->options['model'];
		// grab the login data from the POST request
		$username = $this->controller->data[$model][$this->options['username']];
		$password = $this->controller->data[$model][$this->options['password']];
		
		// if the user is already loggedin we => return true
		if ($this->isLoggedIn(false)) return true;
		
		// loads the User model
		$this->controller->loadModel( $model );
		eval( 
			String::insert('$userRecord = $this->controller->:model->find(
					"first", 
						array(
							"conditions" => 
								array( "LOWER(:model.:userColumn)" => ":username") 
						) 
				);', 
				array(
					'model' => $model,
					'userColumn' => $this->options['username'],
					'username' => strtolower($username),
				) 
			)
		);
		
		// USER NOT FOUND => LOGIN FAILED => return false
			if (!$userRecord) return false;
		
		// USER FOUND
			// if the password is salted append the salt
			if (is_bool($this->options['salt']) && $this->options['salt'])	
				$this->options['salt'] = 'salt';
			if (is_string($this->options['salt']))
				$password .= $userRecord[$model][$this->options['salt']];		
			
			 // hash the salted password (supporting 35 algorithms)
			if (is_string($this->options['hash']))	{
				if (!in_array($this->options['hash'], hash_algos()))
					$this->options['hash'] = "md5";
				$password = hash($this->options['hash'], $password);
			}
			
			// Password hashes do NOT match
			// LOGIN FAILED => return false
				if ($password != $userRecord[$model][$this->options['password']])
					return false;
			
			// LOGIN SUCCESS
				// store the user_id in the session
				$this->Session->write(
						'loggedinUser', 
						$userRecord[$this->options['model']]['id']
				);
				
				$this->user = $userRecord;
				
				// if cookie support is enabled create a longlife cookie
				if (is_bool($this->options['cookies']) && $this->options['cookies'])
					$this->options['cookies'] = 3600 * 24 * 365;
				if (!is_bool($this->options['cookies']))
					$this->Cookie->write('app_uid', $this->getIdentify());
					
				$this->getUser();
				// => return true
				return true;
	}
	
	/**
	 * logs the user out by destroing the session and removing the cookie
	 */
	function logout()	{
		@$this->Cookie->delete('app_uid');
		@$this->Session->destroy();
	}
}